/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package SecretSanta.service.impl;

import SecretSanta.core.HistoryMap;
import SecretSanta.core.Pair;
import SecretSanta.core.Person;
import SecretSanta.service.SantaParser;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Properties;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import resources.stubs.HistoryDataType;
import resources.stubs.MappingType;
import resources.stubs.PersonType;
import resources.stubs.YearType;

/**
 *
 * @author Mallisa
 */
public class SantaParserJaxbImpl implements SantaParser {
    
    private String PROPPATH = "src/resources/config.properties";
    
    private String HISTORYPATH;
    private HistoryDataType completeHistory;
    private Properties prop;
    
    public SantaParserJaxbImpl() {
        HISTORYPATH = getProperty("HISTORYDATAPATH");
    }
    
     /**
     * @param args
     */
    public static void main(String[] args) {
	SantaParserJaxbImpl parser = new SantaParserJaxbImpl();
        
        System.out.println("\n*******getHistory(int,int)*******\n");
        List<HistoryMap> map = parser.getHistory(2009,2010);
        System.out.println("OUTPUT:");
        if (map != null) {
            for(HistoryMap m : map) {
                System.out.println("Year: " + m.getYear());
                List<Pair> pairs = m.getMappings();
                for (Pair p : pairs) {
                    System.out.println("Santa: " + p.getSanta() + " & Reciever: " + p.getReciever());
                }
            }
        }
        
        System.out.println("\n*******getLatestHistory()*******\n");
        HistoryMap latest = parser.getLatestHistory();
        System.out.println("OUTPUT:");
        System.out.println("Year: " + latest.getYear());
        List<Pair> pairs = latest.getMappings();
        for (Pair p : pairs) {
            System.out.println("Santa: " + p.getSanta() + " & Reciever: " + p.getReciever());
        }
        
        System.out.println("\n*******getPeople(List<String>)*******\n");
        List<String> list = new ArrayList<String>();
        list.add("Susan R");
        list.add("Corey T");
        list.add("This is not a person on the list");
        List<Person> people = parser.getPeople(list);
        System.out.println("OUTPUT:");
        for (Person p : people) {
            System.out.println("Name: " + p.getName() + " & Email: " + p.getEmail());
            System.out.print("   Restrictions: [ ");
            for (String s : p.getRestrictions()) {
                System.out.print(s +  " ");
            }
            System.out.println("]");
        }
        
        System.out.println("\n*******getPerson(String)*******\n");
        Person person = parser.getPerson("Riley W");
        System.out.println("OUTPUT:");
        System.out.println("Name: " + person.getName() + " & Email: " + person.getEmail());
        System.out.print("   Restrictions: [ ");
        for (String s : person.getRestrictions()) {
            System.out.print(s +  " ");
        }
        System.out.println("]");
        
//        System.out.println("\n*******addHistoryMap(HistoryMap)*******\n");
//        HistoryMap testMap = new HistoryMap(2008);
//        testMap.addPairing("new3", "new4");
//        parser.addHistoryMap(testMap);
        
//        System.out.println("\n*******updatePeople(List<Person>)*******\n");
//        List<Person> testPeople = new ArrayList<Person>();
//        Person p1 = new Person("p1", "person1@email.com", list);
//        Person p2 = new Person("p2", "person2@email.com");
//        testPeople.add(p1);
//        testPeople.add(p2);
//        parser.updatePeople(testPeople);
    }
    
    /**
     * Returns the mappings of the years within the specified range (either from start-end or end-start)
     * @param starting year (int)
     * @param ending year (int)
     * @return List of mappings (List<HistoryMap>)
     */
    @Override
    public List<HistoryMap> getHistory(int start, int end) {
        System.out.println("[TRACE] Invoked: getHistory(" + start + "," + end + ")");
        if (start > end) {
            int temp = end;
            end = start;
            start = temp;
        }
        List<HistoryMap> result = new ArrayList<HistoryMap>();
        HistoryDataType complete = setHistory();

        List<YearType> years = complete.getHistory().getYear();
        HistoryMap map;
        for( YearType y : years) {
            int year = y.getYr().intValue();
            if (year >= start && year <= end) {
                map = new HistoryMap(year);
                List<MappingType> mappings = y.getMapping();
                for( MappingType m : mappings) {
                    map.addPairing(m.getSanta(), m.getReciever());
                }
                result.add(map);
            }
        }
        System.out.println("[TRACE] Completed: getHistory(" + start + "," + end + "): " +result.size() +" year(s) returned" );
        return result;
    }

    /**
     * Returns the history of the previous year.
     * Ex. If it is currently October 21, 2011, it will return with mappings from 2010 only
     * @return Mappings for previous year (HistoryMap)
     */
    @Override
    public HistoryMap getLatestHistory() {
        System.out.println("[TRACE] Invoked: getLatestHistory()");
        HistoryDataType complete = setHistory();
        List<YearType> years = complete.getHistory().getYear();
        
        if (years.size() > 0) {
            YearType recYear = years.get(years.size()-1);
            
            System.out.println("[TRACE] Completed: getLatestHistory(): " + recYear.getYr().intValue() + " returned");
            
            int year = recYear.getYr().intValue();
            HistoryMap map = new HistoryMap(year);
            List<MappingType> mappings = recYear.getMapping();
            for( MappingType m : mappings) {
                map.addPairing(m.getSanta(), m.getReciever());
            }
            
            return map;
        } else {
            System.out.println("[ERROR] Completed: getLatestHistory(): There are no years recorded; null returned");
            return null;
        }
            
    }

    /**
     * Returns details of people specified in parameters
     * @param names of people (List<String>)
     * @return list of details (List<Person>)
     */
    @Override
    public List<Person> getPeople(List<String> people) {
        System.out.println("[TRACE] Invoked: getPeople(List<String>): " + people.size() + " detail(s) requested");
        List<Person> result = new ArrayList<Person>();
        HistoryDataType complete = setHistory();
        
        List<PersonType> peopleRaw = complete.getPeople().getPerson();
        Person person;
        for (PersonType p : peopleRaw) {
            if (people.contains(p.getName())) {
                person = new Person(
                    p.getName(),
                    p.getEmail(),
                    p.getRestriction());
                result.add(person);
            }
        }
        System.out.println("[TRACE] Completed: getPeople(List<String>): " + result.size() + " detail(s) returned");
        return result;
    }

    /**
     * Retrieves details of any one person
     * @param person's name (String)
     * @return details (Person)
     */
    @Override
    public Person getPerson(String person) {
         System.out.println("[TRACE] Invoked: Person(String): details of " + person + " requested");
         List<String> list = new ArrayList<String>();
         list.add(person);
         
         List<Person> result = getPeople(list);
         
         if (result.size() > 0) {
             System.out.println("[TRACE] Completed: Person(String): details of " + person + " returned");
             return result.get(0);
         } else {
             System.out.println("[ERROR] Completed: Person(String): " + person + " does not exist in records");
             return null;
         }
         
    }

    /**
     * Adds a HistoryMap to the HistoryData.xml
     * If a HistoryMap is added that already exists, the previous one will be overwritten
     * @param history requested to add (HistoryMap)
     * @return true if record is successfully added, else false
     */
    @Override
    public Boolean addHistoryMap(HistoryMap history) {
        System.out.println("[TRACE] Invoked: addHistoryMap(HistoryMap): For year " + history.getYear() );
        HistoryDataType complete = setHistory();
        
        YearType year = new YearType();
        year.setYr(BigInteger.valueOf(history.getYear()));
        
        //Check if record already exists
        if (complete.getHistory().getYear().contains(year)) {
            System.out.println("[WARN] addHistoryMap(HistoryMap): Previous record at year " + year.getYr() + " has been overwritten");
            complete.getHistory().getYear().remove(year);
        }
        
        MappingType m;
        for (Pair p : history.getMappings()) {
            m = new MappingType();
            m.setSanta(p.getSanta());
            m.setReciever(p.getReciever());
            year.getMapping().add(m);
        }
        
        complete.getHistory().getYear().add(year);
        boolean result = writeHistory(complete);
        
        readHistory();  //to ensure current cache is updated
        
        System.out.println("[TRACE] Completed: addHistoryMap(HistoryMap): Success? -> " + result );
        return result;
    }

    /**
     * Updates selected people
     * @param List of people that require updating (List<Person>)
     * @return true if record is successfully added, else false
     */
    @Override
    public Boolean updatePeople(List<Person> people) {
        System.out.println("[TRACE] Invoked: updatePeople(List<Person>): " + people.size() + " request(s)");
        HistoryDataType complete = setHistory();
        
        PersonType person;
        for (Person p : people) {
            
            person = new PersonType();
            person.setName(p.getName());
            person.setEmail(p.getEmail());
            person.getRestriction().addAll(p.getRestrictions());
            
            if (complete.getPeople().getPerson().contains(person)) {
                System.out.println("[WARN] updatePeople(List<Person>): Previous record for " + p.getName() + " has been overwritten");
                complete.getPeople().getPerson().remove(person);
            }
            
            complete.getPeople().getPerson().add(person);
        }
        
        boolean result = writeHistory(complete);
        
        readHistory();  //to ensure current cache is updated
        
        System.out.println("[TRACE] Completed: updatePeople(List<Person>): Success? -> " + result );
        return result;
    }
    
    private void readHistory() {        
        JAXBContext context;
        try {
            File file = new File(HISTORYPATH);
            FileInputStream input = new FileInputStream(file);
            
            context = JAXBContext.newInstance("resources.stubs");
            Unmarshaller unmarshaller = context.createUnmarshaller();
//            JAXBElement<?> historyElement = (JAXBElement<?>) unmarshaller.unmarshal(input);
            HistoryDataType historyElement = (HistoryDataType) unmarshaller.unmarshal(input);
            
            completeHistory = (HistoryDataType) historyElement;          
        } catch (JAXBException je) {
            System.out.println(je.getMessage());
        } catch (IOException ie) {
            System.out.println(ie.getMessage());
        }
    }
    
    private boolean writeHistory(HistoryDataType history) {
        JAXBContext context;
        try {
            File file = new File(HISTORYPATH);
            
            context = JAXBContext.newInstance("resources.stubs");
            Marshaller marshaller = context.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            
            marshaller.marshal(history, new FileOutputStream(file));
//            marshaller.marshal(history, System.out);
            return true;
        } catch (JAXBException je) {
            System.out.println("[ERROR : JAXB] writeHistory(HistoryDataType): " + je.getMessage());
        } catch (FileNotFoundException fe) {
            System.out.println("[ERROR : FileNotFound] writeHistory(HistoryDataType): " + fe.getMessage());
        }
        return false;
    }
    
    private HistoryDataType setHistory() {
        if (completeHistory == null) {
            readHistory();
        }
        return completeHistory;
    }
    
    private String getProperty(String key) {
	if (prop == null) {
            prop = new Properties();
            try {
            	prop.load(new FileInputStream(PROPPATH));
            } catch (IOException e) {
                System.out.println("[Error] " + e.getMessage());
            }
	}
	return prop.getProperty(key);
    }
    
}
